package cl.regalloc.parse;

import java.util.HashSet;

public class Instruction {
	private Opcode opcode;
	private Operand op1;
	private Operand op2;
	private Operand op3;

	private HashSet<Register> refs = new HashSet<Register>();
	private HashSet<Register> defs = new HashSet<Register>();

	private Boolean op1Def = false;
	private Boolean op1Ref = false;
	private Boolean op2Def = false;
	private Boolean op2Ref = false;
	private Boolean op3Def = false;
	private Boolean op3Ref = false;

	public Instruction(Opcode opcode, Register op1, Register op2, Register op3) {
		this.opcode = opcode;
		this.op1 = op1;
		this.op2 = op2;
		this.op3 = op3;
	}

	public Instruction(String currentLine) {
		currentLine.trim();
		if (currentLine == "") {
			this.opcode = null;
			this.op1 = null;
			this.op2 = null;
			this.op3 = null;
			return;
		}
		currentLine = currentLine.replaceAll("	", " ");
		System.out.printf("parsing instruction \"%s\"%n", currentLine);
		String extractedOpcode = currentLine.substring(0, currentLine.indexOf(" ")); /* it's TAB here */

		System.out.printf("opcode \"%s\" extracted!%n", extractedOpcode);

		try {
			this.opcode = new Opcode(extractedOpcode);
		} catch (OpcodeDoesNotExistException e) {
			e.printStackTrace();
		}
		/* the remainder of the instruction */
		currentLine = currentLine.substring(currentLine.indexOf(" ") + 1).trim();

		if (currentLine.contains(",")) {
			String currentOperand1 = currentLine.substring(0, currentLine.indexOf(","));
			this.op1 = getOperand(currentOperand1);
			currentLine = currentLine.substring(currentLine.indexOf(",") + 1,	currentLine.length());
			System.out.printf("parsing 2nd, 3rd operands \"%s\"%n", currentLine);
			/* 2 or 3 operands */
			if (currentLine.contains(",")) {
				/* 3 operands */
				String currentOperand2 = currentLine.substring(0, currentLine.indexOf(","));
				String currentOperand3 = currentLine.substring(currentLine.indexOf(",") + 1, currentLine.length());
				this.op2 = getOperand(currentOperand2);
				this.op3 = getOperand(currentOperand3);
			} else {
				/* 2 operands */
				this.op2 = getOperand(currentLine);
			}
		} else {
			/* 1 operand */
			this.op1 = getOperand(currentLine);
		}

		// extract referenced and/or definied registers
		this.refs = Opcode.identifyRefs(this);
		this.defs = Opcode.identifyDefs(this);
	}

	private static Operand getOperand(String operand) {
		System.out.printf("parsing operand \"%s\"...%n", operand);
		if ((operand == null) || (operand.equals(""))) {
			/* no operand, return null */
			return null;
		} else if (operand.contains("(")) {
			/* lw/sw instructions, return StackLocation */
			return StackLocationFactory.getStackLocation(operand);
		} else if (operand.contains("$")) {
			/* register */
			return RegisterFactory.getRegister(operand);		
		} else if (operand.contains("0x")) {
			/* hex number, remove 0x when passing to HexString */
			return new HexString(operand.substring(2));
		} else try {
			/* dec. no. or label */
			return new IntOperand(Integer.parseInt(operand));
		} catch (NumberFormatException e) {
			/* label */
			return LabelFactory.getLabel(operand);
		}
	}

	public Opcode getOpcode() {
		return opcode;
	}

	public void setOpcode(Opcode opcode) {
		this.opcode = opcode;
	}

	public Operand getOp1() {
		return op1;
	}

	public void setOp1(Operand op1) {
		this.op1 = op1;
	}

	public Operand getOp2() {
		return op2;
	}

	public void setOp2(Operand op2) {
		this.op2 = op2;
	}

	public Operand getOp3() {
		return op3;
	}

	public void setOp3(Operand op3) {
		this.op3 = op3;
	}

	public boolean isConditional() {
		return opcode.isConditional();
	}

	public boolean isJump() {
		return opcode.isJump();
	}

	public boolean addToRefs(Register newRef) {
		if (!this.refs.contains(newRef)) {
			this.refs.add(newRef);
			return true;
		} else return false;
	}

	public HashSet<Register> getRefs() {
		// for use in live variable analysis
		return this.refs;
	}

	public boolean addToDefs(Register newDef) {
		if (!this.defs.contains(newDef)) {
			this.defs.add(newDef);
			return true;
		} else return false;
	}

	public HashSet<Register> getDefs() {
		// for use in live variable analysis
		return this.defs;
	}

	public boolean replaceRefReg(Register oldReg, Register newReg) {
		Boolean rs = false;
		if (this.op1 != null)
			if ((this.op1 == oldReg) && (this.op1Ref)) {
				this.op1 = newReg;
				rs = true;
			}

		if (this.op2 != null)
			if ((this.op2 == oldReg) && (this.op2Ref)) {
				this.op2 = newReg;
				rs = true;
			}

		if (this.op3 != null)
			if ((this.op3 == oldReg) && (this.op3Ref)) {
				this.op3 = newReg;
				rs = true;
			}

		if (this.refs.contains(oldReg)) {
			this.refs.remove(oldReg);
			this.refs.add(newReg);
		}
		return rs;
	}

	public boolean replaceDefReg(Register oldReg, Register newReg) {
		Boolean rs = false;
		if (this.op1 != null)
			if ((this.op1 == oldReg) && (this.op1Def)) {
				this.op1 = newReg;
				rs = true;
			}

		if (this.op2 != null)
			if ((this.op2 == oldReg) && (this.op2Def)) {
				this.op2 = newReg;
				rs = true;
			}

		if (this.op3 != null)
			if ((this.op3 == oldReg) && (this.op3Def)) {
				this.op3 = newReg;
				rs = true;
			}

		// update the refs and defs too!
		if (this.defs.contains(oldReg)) {
			this.defs.remove(oldReg);
			this.defs.add(newReg);
		}
		return rs;
	}

	public boolean isLoad() {
		return opcode.isLoad();
	}

	public boolean hasLabelDst() {
		if (op1 != null)
			if (Label.class.isInstance(op1))
				return true;		
		if (op2 != null)
			if (Label.class.isInstance(op2))
				return true;		
		if (op3 != null)
			if (Label.class.isInstance(op3))
				return true;

		return false;
	}

	public Label getDst() {
		if (op1 != null)
			if (Label.class.isInstance(op1))
				return (Label)op1;		
		if (op2 != null)
			if (Label.class.isInstance(op2))
				return (Label)op2;		
		if (op3 != null)
			if (Label.class.isInstance(op3))
				return (Label)op3;
		return null;
	}

	public String toString() {
		String returnString = this.opcode.toString();
		if (op1 != null)
			returnString = returnString + "	" + this.op1.toString();
		if (op2 != null)
			returnString = returnString + "," + this.op2.toString();
		if (op3 != null)
			returnString = returnString + "," + this.op3.toString();

		return returnString;
	}

	public void setOp1Def() {
		this.op1Def = true;		
	}

	public void setOp1Ref() {
		this.op1Ref = true;
	}

	public void setOp2Def() {
		this.op2Def = true;		
	}

	public void setOp2Ref() {
		this.op2Ref = true;
	}

	public void setOp3Def() {
		this.op3Def = true;		
	}

	public void setOp3Ref() {
		this.op3Ref = true;
	}

	public boolean isMoveRelated() {
		return this.opcode.isMoveRelated();
	}

	public Register getMoveRegA() {
		return (Register) this.op1;
	}

	public Register getMoveRegB() {
		return (Register) this.op2;
	}
}
